﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using OfficeOpenXml;
using ParieurPro.DAL.Scores;
using ParieurPro.DAL.DatabaseModel;

namespace ParieurPro.DAL
{
    public class UpdateScores
    {
        Dictionary<Guid, int> lastRankDictionary = new Dictionary<Guid, int>();
        Dictionary<Guid, int> differentialDictionary = new Dictionary<Guid, int>();
        Dictionary<int, Events> eventsCache = new Dictionary<int, Events>();
        Dictionary<int, byte> eventsCompleted = new Dictionary<int, byte>();

        /// <summary>
        /// Update scores dictionary
        /// </summary>
        /// <param name="currentMatch">Current match to process</param>
        /// <param name="bets">All userbets</param>
        /// <param name="scores">All scores</param>
        private void Update(MyBetDBEntities db, Matchs currentMatch, IList<UserBets> bets, ref Dictionary<Guid, RankObject> scores)
        {
            if (currentMatch == null)
            {
                return;
            }

            var query = from bet in bets
                        where bet.MatchId == currentMatch.MatchId
                        select bet;
            IList<UserBets> betList = query.ToList();
            foreach (UserBets bet in betList)
            {
                RankObject rank = new RankObject();
                UserBets currentBet = bet;
                if (!scores.ContainsKey(bet.UserId))
                {
                    scores.Add(bet.UserId, new RankObject());
                }
                else
                {
                    rank = scores[bet.UserId];
                }
                ClassicScore.ComputeScore(currentMatch, ref currentBet, ref rank);
                BetObject.UpdateBetPoint(db, currentBet);
                scores[bet.UserId] = rank;
            }

        }

        private void AddScores(MyBetDBEntities db, ref Dictionary<Guid, RankObject> total, Dictionary<Guid, RankObject> eventScores, Events evt, int customContestId, ref long bestScore)
        {
            Dictionary<Guid, RankObject> temporaryDictionary = new Dictionary<Guid, RankObject>();
            long maxScore = 0;
            bestScore = -1;

            foreach (Guid userid in eventScores.Keys)
            {
                RankObject rank = eventScores[userid];
                rank.EventCount = 1;

                if (maxScore < rank.TotalScore)
                {
                    maxScore = rank.TotalScore;
                }

                if (total.ContainsKey(userid))
                {
                    if (total[userid].GoodPredictionCount.HasValue)
                    {
                        total[userid].GoodPredictionCount += rank.GoodPredictionCount.Value;
                    }
                    else
                    {
                        total[userid].GoodPredictionCount = rank.GoodPredictionCount.Value;
                    }
                    if (total[userid].GoodScoreCount.HasValue)
                    {
                        total[userid].GoodScoreCount += rank.GoodScoreCount.Value;
                    }
                    else
                    {
                        total[userid].GoodScoreCount = rank.GoodScoreCount.Value;
                    }
                    total[userid].TotalScore += rank.TotalScore;
                    total[userid].EventCount += 1;
                }
                else
                {
                    total.Add(userid, rank);
                }
            }


            // Order rank by totalscore
            var query = from rnk in total
                        orderby rnk.Value.TotalScore descending, rnk.Value.GoodPredictionCount descending, rnk.Value.GoodScoreCount descending
                        select rnk;
            IList<KeyValuePair<Guid, RankObject>> rankList = query.ToList();

            int position = 1;
            foreach (KeyValuePair<Guid, RankObject> pair in rankList)
            {
                RankObject tmpRank = pair.Value;
                tmpRank.Rank = position;
                temporaryDictionary.Add(pair.Key, tmpRank);
                position++;
            }

            if (eventScores.Count > 0)
            {
                foreach (Guid userid in total.Keys)
                {
                    RankObject rank;
                    //byte isBestScore = 0;

                    if (eventScores.ContainsKey(userid))
                    {
                        rank = eventScores[userid];
                        if (rank.TotalScore == maxScore)
                        {
                            bestScore = maxScore;
                        }
                    }
                    else
                    {
                        rank = new RankObject();
                    }

                    rank.Rank = temporaryDictionary[userid].Rank;
                    // Store event score in database
                    EventObject.AddOrUpdateEventScore(db, userid, evt, rank, (rank.TotalScore==bestScore), eventsCompleted[evt.EventId], customContestId);

                    if (lastRankDictionary.ContainsKey(userid))
                    {
                        if (differentialDictionary.ContainsKey(userid))
                        {
                            differentialDictionary[userid] = lastRankDictionary[userid] - rank.Rank;
                        }
                        else
                        {
                            differentialDictionary.Add(userid, lastRankDictionary[userid] - rank.Rank);
                        }
                        lastRankDictionary[userid] = rank.Rank;
                    }
                    else
                    {
                        lastRankDictionary.Add(userid, rank.Rank);
                        differentialDictionary.Add(userid, 0);
                    }
                }
            }
        }

        ///// <summary>
        ///// Create or update rank
        ///// </summary>
        ///// <param name="db"></param>
        ///// <param name="total"></param>
        //private void UpdateRank(MyBetDBEntities db, Dictionary<Guid, Ranks> total)
        //{
        //    List<Ranks> ranks = new List<Ranks>();
        //    //int position = 1;

        //    foreach (Guid userId in total.Keys)
        //    {
        //        // Get subscription id
        //        int id = SubscriptionObject.GetSubscriptionId(db, userId);
        //        Ranks rank = total[userId];
        //        rank.CurrentRank = lastRankDictionary[userId];
        //        rank.RankDifferential = null;
        //        if (differentialDictionary.ContainsKey(userId))
        //        {
        //            rank.RankDifferential = differentialDictionary[userId];
        //        }
        //        rank.SubscriptionId = id;
        //        RankObject.AddOrUpdate(db, rank);
        //    }
        //}

        /// <summary>
        /// Create or update rank
        /// </summary>
        /// <param name="db"></param>
        /// <param name="total"></param>
        private void UpdateRank(MyBetDBEntities db, Events evt, Dictionary<Guid, RankObject> total, long bestScore, int customContestId)
        {
            List<Ranks> ranks = new List<Ranks>();
            //int position = 1;

            foreach (Guid userId in total.Keys)
            {
                // Get subscription id
                int id = SubscriptionObject.GetSubscriptionId(db, userId, customContestId);
                RankObject rank = total[userId];
                if (lastRankDictionary.ContainsKey(userId))
                {
                    rank.Rank = lastRankDictionary[userId];
                    rank.RankDifferential = null;
                    if (differentialDictionary.ContainsKey(userId))
                    {
                        rank.RankDifferential = differentialDictionary[userId];
                    }
                    //rank.SubscriptionId = id;

                    RankObject.AddOrUpdateFromObject(db, rank, id, rank.Rank, rank.RankDifferential);

                    //EventObject.AddOrUpdateEventScore(db, userId, evt, rank, (rank.TotalScore == bestScore), eventsCompleted[evt.EventId], customContestId);
                }
            }
        }

        public void UpdateFromFile(String fileName)
        {
            System.IO.FileInfo fileIn = new System.IO.FileInfo(fileName);
            Dictionary<string, int> teamsId = new Dictionary<string,int>();

            using (ExcelPackage pkg = new ExcelPackage(fileIn))
            {
                // get the first worksheet in the workbook
                ExcelWorksheet worksheet = pkg.Workbook.Worksheets[1];
                // Read first line to get ChampionshipId and seasonId
                int championshipId = Convert.ToInt32(worksheet.Cells[1, 2].Value);
                //int currentCustomContest = 1;
                int seasonId = Convert.ToInt32(worksheet.Cells[1, 4].Value);
                bool isCreationMode = (Convert.ToInt32(worksheet.Cells[1, 6].Value) == 1);

                // File is finished ?
                using (MyBetDBEntities db = new MyBetDBEntities())
                {
                    int? contestId = ContestObject.GetId(db, championshipId, seasonId);
                    if (!contestId.HasValue)
                    {
                        throw new ApplicationException("No contest found");
                    }
                    // Récupération de la liste des tournois sur cette compétition
                    List<int> customContestsList = CustomContestObject.GetCustomContestsIdListFromContestId(db, contestId.Value);

                    foreach (int id in customContestsList)
                    {
                        UpdateCustomContestScore(teamsId, worksheet, championshipId, id, seasonId, isCreationMode, db, contestId);
                    }

                }

            }
        
        }

        private void UpdateCustomContestScore(Dictionary<string, int> teamsId, ExcelWorksheet worksheet, int championshipId, int currentCustomContestId, int seasonId, bool isCreationMode, MyBetDBEntities db, int? contestId)
        {
            // Read match information
            Dictionary<Guid, RankObject> eventScores = new Dictionary<Guid, RankObject>();
            IList<UserBets> bets = new List<UserBets>();
            // Current day
            int currentEvent = 0;
            // Current line
            int line = 2;
            bool bContinue = true;
            DateTime dayDate = DateTime.UtcNow;
            DateTime closeDate = DateTime.UtcNow;
            DateTime openDate = DateTime.UtcNow;
            Events evt = null;
            Dictionary<Guid, RankObject> scores = new Dictionary<Guid, RankObject>();
            long bestScore=-1;

            lastRankDictionary.Clear();
            differentialDictionary.Clear();

            IList<Guid> users = SubscriptionObject.GetUsersList(db, currentCustomContestId);
            foreach (Guid user in users)
            {
                scores.Add(user, new RankObject());
            }

            eventsCompleted = new Dictionary<int, byte>();

            do
            {
                bContinue = (worksheet.Cells[line, 1].Value != null);
                if (bContinue)
                {
                    int dayNumber = Convert.ToInt32(worksheet.Cells[line, 1].Value);
                    if (currentEvent != dayNumber)
                    {
                        
                        // Store event score somewhere
                        //this.Invoke(this.ResultDelegate, new object[] { String.Format("Process {0} day", dayId) });

                        // Par défaut on considère que tous les scores de l'événement sont renseignés
                        
                        if (evt != null)
                        {
                            if (evt.CloseDate < DateTime.UtcNow)
                            {
                                AddScores(db, ref scores, eventScores, evt, currentCustomContestId, ref bestScore);
                            }
                        }
                        eventScores.Clear();

                        dayDate = DateTime.FromOADate(Convert.ToDouble(worksheet.Cells[line, 2].Value));
                        dayDate = new DateTime(dayDate.Year, dayDate.Month, dayDate.Day, 19, 0, 0);
                        TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time");
                        dayDate = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(dayDate, tzi.Id, TimeZoneInfo.Utc.Id);
                        closeDate = new DateTime(dayDate.Year, dayDate.Month, dayDate.Day, dayDate.Hour, dayDate.Minute, dayDate.Second);
                        openDate = closeDate.AddDays(-3);
                        // Verify if day exists in database
                        if (!eventsCache.ContainsKey(dayNumber))
                        {
                            if (isCreationMode)
                            {
                                evt = EventObject.GetEventFromDatabase(db, championshipId, seasonId, dayNumber);
                                EventObject.AddOrUpdateEvent(db, evt, contestId.Value, dayNumber, openDate, closeDate);
                            }
                            evt = EventObject.GetEventFromDatabase(db, championshipId, seasonId, dayNumber);
                            eventsCache.Add(dayNumber, evt);
                        }
                        else
                        {
                            evt = eventsCache[dayNumber];
                        }

                        eventsCompleted.Add(evt.EventId, 1);
                        currentEvent = dayNumber;
                        // Get all bets for this event
                        bets = BetObject.GetCustomContestBetsInDatabase(db, championshipId, seasonId, dayNumber, currentCustomContestId);
                    }

                    string dayHomeTeam = Convert.ToString(worksheet.Cells[line, 3].Value);
                    byte? dayScoreHome = null;
                    byte? dayScoreAway = null;
                    string dayAwayTeam = Convert.ToString(worksheet.Cells[line, 6].Value);
                    bool isMatchOfTheDay = false; 
                    int tid = -1;
                    Matchs currentMatch;

                    if (worksheet.Cells[line, 7].Value != null)
                    {
                        isMatchOfTheDay = (Convert.ToByte(worksheet.Cells[line, 7].Value)==1);
                    }

                    if (worksheet.Cells[line, 4].Value != null)
                    {
                        dayScoreHome = Convert.ToByte(worksheet.Cells[line, 4].Value);
                    }
                    if (worksheet.Cells[line, 5].Value != null)
                    {
                        dayScoreAway = Convert.ToByte(worksheet.Cells[line, 5].Value);
                    }

                    // On vérifie si l'événement est complet
                    if ((!dayScoreHome.HasValue) || (!dayScoreAway.HasValue))
                    {
                        // Evenement incomplet
                        if (evt != null)
                        {
                            eventsCompleted[evt.EventId] = 0;
                        }
                    }

                    if (!teamsId.ContainsKey(dayHomeTeam.ToUpper()))
                    {
                        tid = TeamObject.GetTeamId(db, dayHomeTeam.ToUpper());
                        if (tid == -1)
                        {
                            throw new System.Exception("Unknown team " + dayHomeTeam);
                        }
                        teamsId.Add(dayHomeTeam.ToUpper(), tid);
                    }
                    if (!teamsId.ContainsKey(dayAwayTeam.ToUpper()))
                    {
                        tid = TeamObject.GetTeamId(db, dayAwayTeam.ToUpper());
                        if (tid == -1)
                        {
                            throw new System.Exception("Unknown team " + dayAwayTeam);
                        }
                        teamsId.Add(dayAwayTeam.ToUpper(), tid);
                    }
                    // update match in database

                    currentMatch = MatchObject.AddOrUpdate(db, closeDate, teamsId[dayHomeTeam.ToUpper()], teamsId[dayAwayTeam.ToUpper()], dayScoreHome, dayScoreAway, evt.EventId, isMatchOfTheDay);

                    if (evt.CloseDate < DateTime.UtcNow)
                    {
                        Update(db, currentMatch, bets, ref eventScores);
                    }
                    else
                    {
                        System.Diagnostics.Trace.WriteLine("not started...");
                    }

                    line = line + 1;
                }

            } while (bContinue);

            
            if (evt.CloseDate < DateTime.UtcNow)
            {
                AddScores(db, ref scores, eventScores, evt, currentCustomContestId, ref bestScore);
            }
            // Store current rank somewhere
            UpdateRank(db, evt, scores, bestScore, currentCustomContestId);
            db.SaveChanges(System.Data.Objects.SaveOptions.DetectChangesBeforeSave);
        }


    }
}